﻿using System;

namespace ASquared.ModelOptimization
{
    /// <summary>A class that defines an interval.</summary>
    public class Bounds
    {
        /// <summary>Gets the default tolerance value of the bounds variables.</summary>
        public static Double DefaultTolerance { get { return 0.001; } }

        // Instance variables
        /// <summary>Defines the upper bound</summary>
        public Double Upper;
        /// <summary>Defines the lower bound</summary>
        public Double Lower;
        /// <summary>Defines whether upper bound is strict or not. If strict, a value cannot equal the upper bound.</summary>
        public bool StrictUpperBound;
        /// <summary>Defines whether lower bound is strict or not. If strict, a value cannot equal the lower bound.</summary>
        public bool StrictLowerBound;
        /// <summary>Specifies the tolerance at which numbers are compared to the bounds of this instance.</summary>
        public Double Tolerance;

        // Constructors
        /// <summary>Creates bounds.</summary>
        public Bounds()
            : this(Double.PositiveInfinity, Double.NegativeInfinity)
        {
        }
        /// <summary>Creates bounds.</summary>
        /// <param name="lower">The lower bound.</param>
        /// <param name="upper">The upper bound.</param>
        public Bounds(Double lower, Double upper)
            : this(lower, upper, true)
        {
        }
        /// <summary>Creates bounds.</summary>
        /// <param name="lower">The lower bound.</param>
        /// <param name="upper">The upper bound.</param>
        /// <param name="strictbounds">Specifies whether the lower and upper bounds are strict (a value cannot be equal to either of the bounds).</param>
        public Bounds(Double lower, Double upper, bool strictbounds)
            : this(lower, upper, strictbounds, strictbounds)
        {
        }
        /// <summary>Creates bounds.</summary>
        /// <param name="lower">The lower bound.</param>
        /// <param name="upper">The upper bound.</param>
        /// <param name="strictlowerbound">Specifies whether the lower bound is strict (a value cannot be equal to the bound).</param>
        /// <param name="strictupperbound">Specifies whether the upper bound is strict (a value cannot be equal to the bound).</param>
        public Bounds(Double lower, Double upper, bool strictlowerbound, bool strictupperbound)
            : this(lower, upper, strictlowerbound, strictupperbound, Bounds.DefaultTolerance)
        {
        }
        /// <summary>Creates bounds.</summary>
        /// <param name="lower">The lower bound.</param>
        /// <param name="upper">The upper bound.</param>
        /// <param name="strictlowerbound">Specifies whether the lower bound is strict (a value cannot be equal to the bound).</param>
        /// <param name="strictupperbound">Specifies whether the upper bound is strict (a value cannot be equal to the bound).</param>
        /// <param name="tolerance">Specifies the tolerance at which numbers are compared to the bounds of this instance.</param>
        public Bounds(Double lower, Double upper, bool strictlowerbound, bool strictupperbound, Double tolerance)
        {
            this.Lower = lower;
            this.Upper = upper;
            this.StrictLowerBound = strictlowerbound;
            this.StrictUpperBound = strictupperbound;
            this.Tolerance = tolerance;
            this.Fix();
        }

        // Methods 
        /// <summary>Switches the upper and lower bounds if they are out of order.</summary>
        public Bounds Fix()
        {
            if (this.Upper < this.Lower)
            {
                Double val = this.Upper;
                this.Upper = this.Lower;
                this.Lower = val;
            }
            return this;
        }
        /// <summary>Creates a <c>Bounds</c> object that mirrors this instance about zero.</summary>
        public Bounds Mirror()
        {
            Bounds retVal = this.Fix();
            retVal.Lower = -this.Upper;
            retVal.Upper = -this.Lower;
            return retVal;
        }
        /// <summary>Creates a <c>Bounds</c> object with negative values of Upper and Lower.</summary>
        public Bounds ToNegative()
        {
            this.Fix();
            if (Math.Abs(this.Upper) > Math.Abs(this.Lower))
                return new Bounds(-Math.Abs(this.Upper), -Math.Abs(this.Lower), this.StrictUpperBound, this.StrictLowerBound);
            else
                return new Bounds(-Math.Abs(this.Lower), -Math.Abs(this.Upper), this.StrictLowerBound, this.StrictUpperBound);
        }
        /// <summary>Creates positive <c>Bounds</c>.</summary>
        public Bounds ToPositive()
        {
            this.Fix();
            if (Math.Abs(this.Upper) > Math.Abs(this.Lower))
                return new Bounds(Math.Abs(this.Lower), Math.Abs(this.Upper), this.StrictLowerBound, this.StrictUpperBound);
            else
                return new Bounds(Math.Abs(this.Upper), Math.Abs(this.Lower), this.StrictUpperBound, this.StrictLowerBound);
        }
        /// <summary>Copies this instance.</summary>
        public Bounds Copy()
        {
            return new Bounds(this.Lower, this.Upper, this.StrictLowerBound, this.StrictUpperBound);
        }
        /// <summary>Returns a value that lies within the bounds.</summary>
        /// <param name="value">The value to check.</param>
        public Double ClosestBoundedValue(Double value)
        {
            this.Fix();
            if (value <= this.Lower)
            {
                if (this.StrictLowerBound)
                    return this.Lower + Tolerance;
                else
                    return this.Lower;
            }
            else if (value >= this.Upper)
            {
                if (this.StrictUpperBound)
                    return this.Upper - Tolerance;
                else
                    return this.Upper;
            }
            return value;
        }
        /// <summary>Returns an integer value that lies within the bounds.</summary>
        /// <param name="value">The value to check.</param>
        public long ClosestBoundedInteger(Double value)
        {
            this.Fix();
            long val = (long)this.ClosestBoundedValue(value);
            if (!this.IsOutsideBounds(val)) return val;
            if (val <= this.Lower)
                return val + 1;
            else //(val >= this.Upper) 
                return val - 1;
        }
        /// <summary>Determines if the value is outside of the bounds.</summary>
        /// <param name="value">Value to check.</param>
        /// <returns>Returns true if the value is outside of the bounds; otherwise, returns false.</returns>
        public bool IsOutsideBounds(Double value)
        {
            this.Fix();
            return value < this.Lower || value > this.Upper
                || (this.StrictUpperBound && this.IsOnUpperBound(value))
                || (this.StrictLowerBound && this.IsOnLowerBound(value));
        }
        /// <summary>Determines if the value lies on the upper bound.</summary>
        /// <param name="value">The value to check.</param>
        /// <returns>Returns true if the value lies on the upper bound. Otherwise, returns false.</returns>
        public bool IsOnUpperBound(Double value)
        {
            return Math.Abs(value - this.Upper) < Tolerance;
        }
        /// <summary>Determines if the value lies on the lower bound.</summary>
        /// <param name="value">The value to check.</param>
        /// <returns>Returns true if the value lies on the lower bound. Otherwise, returns false.</returns>
        public bool IsOnLowerBound(Double value)
        {
            return Math.Abs(value - this.Lower) < Tolerance;
        }

        // Building and exporting as string 
        /// <summary>Builds a string that fully represents the bounds within this instance.</summary>
        public override string ToString()
        {
            string beg = this.StrictLowerBound ? "(" : "[";
            string end = this.StrictUpperBound ? ")" : "]";
            return beg + this.Lower.ToString() + "," + this.Upper.ToString() + end + "±" + this.Tolerance.ToString();
        }
        /// <summary>Interprets a Bounds object from a string representation.</summary>
        /// <param name="bounds">The string representation of the bounds object.</param>
        public static Bounds FromString(string bounds)
        {
            bounds = bounds.Replace("+-", "±");
            string[] parts = bounds.Split(new char[] { ',', '±' }, StringSplitOptions.RemoveEmptyEntries); 
            if (parts.Length <= 1) return new Bounds();

            // Get upper and lower bound strictness 
            Double upper = Double.PositiveInfinity, lower = Double.NegativeInfinity;
            Double tolerance = 0.001; 
            bool strictUpper = true, strictLower = true;
            if (parts.Length >= 2)
            {
                if (parts[0].Trim().StartsWith("["))
                    strictLower = false;
                if (parts[1].Trim().EndsWith("]"))
                    strictUpper = false;
            }
            // Clean the string
            for (Int32 i = 0; i < parts.Length; i++)
                parts[i] = parts[i].Replace("(", "").Replace(")", "").Replace("[", "").Replace("]", "").Replace(" ", "").Replace("±", "");
            lower = Convert.ToDouble(parts[0]);
            upper = Convert.ToDouble(parts[1]);
            if (parts.Length > 2) tolerance = Convert.ToDouble(parts[2]);
            return new Bounds(lower, upper, strictLower, strictUpper, tolerance);    
        }
    }
}
